#include <cstdlib>
#include <iostream>
#include <limits>
#include <string>
#include <ctime>
#include <cstring>
#include <fstream>
#include <getopt.h>
#include <unistd.h>
#include "rules/rules.h"
#include "functions.h"
#ifdef _OPENMP 
#include <omp.h>
#endif

#define INRANGE(i, min, max) (i < min) || (i > max) ? 0 : 1
#define REGLE_ARG(R) (strcmp(optarg, "1")==0) ? rules[R]=true : rules[R]=false
using namespace std;

bool rules[] = {
    false, //Ne sert à rien car indice 0, plus pratique pour avoir règle 1 -> indice 1...
    true,
    false, //rejet règle : http://www.crack-wifi.com/forum/viewtopic.php?id=5559&p=18#p47367
    true,
    true,
    true,
    true,
    true,
    true,
    true,
    false,
};

int main(int argc, char **argv) {
    bool pipe_out = !isatty(STDOUT_FILENO);
    bool pipe_in = !isatty(STDIN_FILENO);
    bool type_cle = true;
    string out = "";
    unsigned long int nb = 0;
    int nbChiffres_essid;
    bool do_essid = false;
    std::string essid;


    cerr << "\t\tlivedico r" <<
#ifdef REV
            REV
#else
            "?"
#endif
            << endl;
    int c;
    static struct option long_options[] = {
        {"r1", required_argument, 0, 1},
        {"r2", required_argument, 0, 2},
        {"r3", required_argument, 0, 3},
        {"r4", required_argument, 0, 4},
        {"r5", required_argument, 0, 5},
        {"r6", required_argument, 0, 6},
        {"r7", required_argument, 0, 7},
        {"r8", required_argument, 0, 8},
        {"r9", required_argument, 0, 9},
        {"r10", required_argument, 0, 10},
        {"out", required_argument, 0, 'o'},
        {"type", required_argument, 0, 't'},
        {"help", no_argument, 0, 'h'},
        {"essid", required_argument, 0, 's'},
        {0, 0, 0, 0}
    };
    while (1) {
        c = getopt_long_only(argc, argv, "ho:n:", long_options, NULL);
        if (c == -1)
            break;
        switch (c) {
            case 'h':
                cout << endl << "livedico [OPTIONS]" << endl << endl;
                cout << "-t TYPE \t type de cle avec TYPE est 1 (0~) ou 2 (LK)" << endl;
                cout << "--type TYPE" << endl << endl;
                cout << "-n NB_CLES \t nombre de cles a generer" << endl << endl;
                cout << "-o FICHIER \t fichier de sortie" << endl;
                cout << "--out FICHIER" << endl << endl;
                cout << "-rX BOOL \t activer ou desactiver la regle numero X" << endl;
                cout << "\t \t BOOL peut etre 1 ou 0" << endl;
                cout << "--essid WXYZ \t fin du essid de la box : Livebox-WXYZ" << endl;
                exit(EXIT_SUCCESS);
                break;
            case 'o':
                out = optarg;
                break;
            case 'n':
                nb = atol(optarg);
                break;
            case 't':
                if (optarg[0] == '1') {
                    type_cle = false;
                } else if (optarg[0] == '2') {
                    type_cle = true;
                } else {
                    cerr << "Type de cle inconnu !" << endl;
                    exit(EXIT_FAILURE);
                }
                break;
            case 's':
                essid = optarg;
                if (essid.length() != 4) {
                    cerr << "essid incorrect !" << endl;
                    exit(EXIT_FAILURE);
                }
                essid = "0x" + essid;
                do_essid = true;
                nbChiffres_essid = rule_essid(strtoul(essid.c_str(), NULL, 0));
                break;
            default:
                if (INRANGE(c, 1, 10)) {
                    REGLE_ARG(c);
                }
        }
    }

    if (nb == 0) {
        if (pipe_out || pipe_in) {
            cerr << "Nombre de cles manquant !" << endl;
            exit(EXIT_FAILURE);
        }
        while ((cout << "Nombre de cles a generer : ")
                && (!(cin >> nb) || nb < 1)) {
            cout << "Ce n'est pas un nombre valide !" << endl;
            cin.clear();
            cin.ignore(numeric_limits<streamsize>::max(), '\n');
        }
    }
    if (out.length() == 0 && !pipe_out) {
        if (pipe_in) {
            cerr << "Fichier de sortie manquant !" << endl;
            exit(EXIT_FAILURE);
        }
        cout << "Fichier : ";
        cin >> out;
    }
    if (pipe_in) {
        nb++; //Pour etre sur de toutes les avoir !
        cerr << "Verification de " << nb << " cles" << endl;
    } else {
        cerr << "Generation de " << nb << " cles (" << round((double) nb * 28.0 / 1024.0 / 1024.0, 3) << " Mo)" << endl;
    }

    unsigned long int i = 0;
    unsigned long long int fKey = 0;
    unsigned long long int total = 0;
    ofstream f;
    if (!pipe_out) {
        f.open(out.c_str(), ios::app);
        if (!f.is_open()) {
            cerr << "Impossible d'ouvrir '" << out << "'" << endl;
            exit(EXIT_FAILURE);
        }
    }
    bool regen = false;
    string key;
    unsigned int seed;
    clock_t end;
    bool done;
    clock_t start = clock();
#pragma omp parallel default(shared) private(done, regen, key, seed)
    {
#ifndef _OPENMP
        seed = time(NULL);
#else
        seed = time(NULL)*(omp_get_thread_num() + 1);
#endif
#pragma omp for schedule(guided)
        for (i = 0; i < nb; i++) {
            done = false;
            while (!done) {
                regen = false;
                if (pipe_in) {
#pragma omp critical
                    getline(cin, key);
                    //cerr << key << endl;
                } else {
                    key = genKey(type_cle, i, &seed, rules);
                }
#pragma omp atomic
                total++;
                if (pipe_in) {
                    if (key.length() != 26) regen = true;
                    for (int c = 0; c < key.length(); c++) {
                        if (rule1(type_cle).find(key[c]) == string::npos) regen = true;
                    }
                    if (rules[2])
                        if (string("0123456789").find(key[22]) == string::npos) regen = true;
                }

                if (rules[3])
                    if (!rule3(key)) regen = true;
                if (rules[4])
                    if (!rule4(key)) regen = true;
                if (type_cle == true) {
                    if (rules[5])
                        if (!rule5(key)) regen = true;
                    if (rules[6])
                        if (!rule6(key)) regen = true;
                    if (rules[7])
                        if (!rule7(key))regen = true;
                }
                if (rules[8])
                    if (!rule8(key, type_cle)) regen = true;
                if (rules[9])
                    if (!rule9(key, type_cle)) regen = true;

                if (do_essid && type_cle == true)
                    if (!nbChiffres(key, nbChiffres_essid)) regen = true;

                if (regen == false) {
                    if (pipe_out) {
#pragma omp critical
                        cout << key << endl;
                    } else {
#pragma omp critical
                        f << key << endl;
                    }
                    done = true;
                } else {
#pragma omp atomic
                    fKey++;
                    if (pipe_in) done = true;
                }
            }
        }
    }
    end = clock();
    if (!pipe_out)
        f.close();
    cerr << "Vitesse : " << (double) total / (double) ((end - start) / CLOCKS_PER_SEC) << " cles/s" << endl;
    if (pipe_in) cerr << fKey << " cles etaient fausses..." << endl;

    return EXIT_SUCCESS;
}
